V8 引擎的 BUG
console.log("script start");
async function async1() {
// 如果 await 函数后面的函数是普通函数,那么其后的微任务就正常执行;否则,会将其再放入微任务队列。
await async2();
console.log("async1 end");
}
async function async2() {
// 获取也可以认为这里是异步函数的构造函数,会同步执行的。这么理解也 ok
console.log("async2 end");
}
async1();
setTimeout(function () {
console.log("setTimeout");
}, 0);
new Promise((resolve) => {
console.log("Promise");
resolve();
})
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
});
console.log("script end");
// 在`Chrome 66`和`node v10`中,正确输出是
// script start
// async2 end
// Promise
// script end
// promise1
// promise2
// async1 end
// setTimeout
// 注意:在新版本的浏览器中,`await`输出顺序被“提前”了。
// script start
// async2 end
// Promise
// script end
// async1 end
// promise1
// promise2
// setTimeout
当我们调用 async1 函数时,会马上输出 async2 end,并且函数返回一个 Promise,接下来在遇到 await的时候会就让出线程开始执行 async1 外的代码,所以我们完全可以把 await 看成是让出线程的标志。
async function async1() {
await async2();
console.log("async1 end");
}
// 约等于下面的实现
function async1() {
return new Promise((resolve, reject) => {
console.log("async2 end");
// Promise.resolve() 将代码插入微任务队列尾部
// resolve 再次插入微任务队列尾部
resolve(Promise.resolve());
}).then(() => {
console.log("async1 end");
});
}
也就是说,如果 await 后面跟着 Promise 的话,async1 end 需要等待三个 tick 才能执行到。V8 团队借鉴了 Node 8 中的一个 Bug,在引擎底层将三次 tick 减少到了二次 tick。
再谈 async 和 await
细心的朋友肯定会发现前面第 6 步,如果async2函数是没有async关键词修饰的一个普通函数呢?
// 新的async2函数
function async2() {
console.log("async2 end");
}
输出结果如下所示:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
不同的结果就出现在前面所说的第 6 步:如果 await 函数后面的函数是普通函数,那么其后的微任务就正常执行;否则,会将其再放入微任务队列。
其实是 V8 引擎的 BUG
看到前面,正常人都会觉得真奇怪!(但是按照上面的诀窍倒也是可以理解)
然而 V8 团队确定了这是个 bug(很多强行解释要被打脸了),具体的 PR请看这里。好在,这个问题已经在最新的 Chrome 浏览器中被修复了。
简单点说,前面两段不同代码的运行结果都是:
script start
async2 end
Promise
script end
async1 end
promise1
promise2
setTimeout
await就是让出线程,其后的代码放入微任务队列(不会再多一次放入的过程),就这么简单了。
异步笔试题
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error("p1 中failure")), 3000);
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000);
});
var p3 = new Promise(function (resolve, reject) {
resolve(2);
});
var p4 = new Promise(function (resolve, reject) {
reject(new Error("error in p4"));
});
p3.then((re) => console.log(re)); //?
p4.catch((error) => console.log(error)); //?
p2.then(null, (re) => console.log(re)); //?
p2.catch((re) => console.log(re)); //?
// 2, "error in p4 "这是立即打印出来的。
// 3S 后会打印出两个'p1 中 failure'
var p1 = Promise.resolve(1);
var p2 = new Promise((resolve) => {
setTimeout(() => resolve(2), 100);
});
var v3 = 3;
var p4 = new Promise((resolve, reject) => {
setTimeout(() => reject("oops"), 10);
});
var p5 = new Promise((resolve) => {
setTimeout(() => resolve(5), 0);
});
Promise.race([v3, p1, p2, p4, p5]).then((val) => console.log(val)); //?
Promise.race([p1, v3, p2, p4, p5]).then((val) => console.log(val)); // ?
Promise.race([p1, p2, p4, p5]).then((val) => console.log(val)); // ?
Promise.race([p2, p4, p5]).then((val) => console.log(val)); //?
打印顺序是:3 1 1 5
function Promise1() {
return new Promise(function (resolve, reject) {
for (let i = 0; i < 2; i++) {
console.log("111");
}
resolve(true);
});
}
function Promise2() {
return new Promise(function (resolve, reject) {
for (let i = 0; i < 2; i++) {
console.log("222");
}
resolve(true);
});
}
setTimeout(function () {
console.log("333");
}, 0); // 这是是会执行的。考察的是异步执行,js的任务队列
Promise.all([Promise1(), Promise2()]).then(function () {
console.log("All Done!");
});
// '111'
// '111'
// '222'
// '222'
// 'All Done!'
// '333'
// 请写出输出内容
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
变式一
在第一个变式中我将 async2 中的函数也变成了 Promise 函数,代码如下:
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
//async2做出如下更改:
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise3");
resolve();
}).then(function () {
console.log("promise4");
});
console.log("script end");
可以先自己看看输出顺序会是什么,下面来公布结果:
script start
async1 start
promise1
promise3
script end
promise2
async1 end
promise4
setTimeout
在第一次 macro-task 执行完之后,也就是输出script end之后,会去清理所有 micro-task。所以会相继输出promise2,async1 end ,promise4,其余不再多说。
变式二
在第二个变式中,我将 async1 中 await 后面的代码和 async2 的代码都改为异步的,代码如下:
async function async1() {
console.log("async1 start");
await async2();
//更改如下:
setTimeout(function () {
console.log("setTimeout1");
}, 0);
}
async function async2() {
//更改如下:
setTimeout(function () {
console.log("setTimeout2");
}, 0);
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout3");
}, 0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");
可以先自己看看输出顺序会是什么,下面来公布结果:
script start
async1 start
promise1
script end
promise2
setTimeout3
setTimeout2
setTimeout1
在输出为promise2之后,接下来会按照加入 setTimeout 队列的顺序来依次输出,通过代码我们可以看到加入顺序为3 2 1,所以会按 3,2,1 的顺序来输出。
变式三
变式三是我在一篇面经中看到的原题,整体来说大同小异,代码如下:
async function a1() {
console.log("a1 start");
await a2();
console.log("a1 end");
}
async function a2() {
console.log("a2");
}
console.log("script start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
// Promise.resolve() 返回一个已经执行完毕状态的 Promise
Promise.resolve().then(() => {
console.log("promise1");
});
a1();
let promise2 = new Promise((resolve) => {
resolve("promise2.then");
// Promise 构造函数中除了 resolve 返回部分,其余全是同步执行
console.log("promise2");
});
promise2.then((res) => {
console.log(res);
Promise.resolve().then(() => {
console.log("promise3");
});
});
console.log("script end");
// script start
// a1 start
// a2
// promise2
// script end
// promise1
// a1 end
// promise2.then
// promise3
// setTimeout
setTimeout(() => {
console.log(1);
}, 0);
new Promise((resolve) => {
// Promise 构造函数是同步执行
console.log(2);
resolve();
console.log(3);
}).then(() => {
console.log(4);
});
console.log(5);
// 2 3 5 4 1
Promise.resolve()
.then(() => {
console.log(0);
// 相当于 Promise.resolve(Promise.resolve(4)) Promise.resolve 执行一次相当于将任务往微任务末尾插入一次
return Promise.resolve(4);
})
.then((res) => console.log(res || "xxxx"));
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
})
.then(() => {
console.log(6);
});
// 0, 1, 2, 3, 4, 5, 6
// 状态只会改变一次
const promise = new Promise((resolve, reject) => {
resolve("success1");
reject("error");
resolve("success2");
});
promise
.then((res) => {
console.log("then: ", res);
})
.catch((err) => {
console.log("catch: ", err);
});
// then: success1
// Promise {<fulfilled>: "success1"}
// 可以一直 then catch
Promise.resolve(1)
.then((res) => {
console.log(res);
return 2;
})
.catch((err) => {
return 3;
})
.then((res) => {
console.log(res);
});
// 1, 2
Promise.resolve()
.then(() => {
return new Error("error!!!");
})
.then((res) => {
console.log("then: ", res);
})
.catch((err) => {
console.log("catch: ", err);
});
// then: Error: error!!!
Promise.resolve()
.then(() => {
throw new Error("error!!!");
})
.then((res) => {
console.log("then: ", res);
})
.catch((err) => {
console.log("catch: ", err);
});
// catch: Error: error!!!
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("once");
resolve("success");
}, 1000);
});
const start = Date.now();
// 分别加上 then, 约等于注册了两个事件,会各自获取状态。 链式调用则会包裹一层 Promise.resolve
promise.then((res) => {
console.log(res, Date.now() - start);
});
promise.then((res) => {
console.log(res, Date.now() - start);
});
// once
// success 1004
// success 1005
const promise = Promise.resolve().then(() => {
return promise;
});
promise.catch(console.error);
// 会报错
// TypeError: Chaining cycle detected for promise
// then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
Promise.resolve(1)
.then((res) => {
console.log(res); // => 1
return 2; // 包装成 Promise.resolve(2)
})
.then((res) => {
console.log(res); // => 2
});
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);
// 1
// then 需要传入一个合法的回调,否则不执行
Promise.resolve()
.then(
function success(res) {
throw new Error("error");
},
function fail1(e) {
console.error("fail1: ", e);
}
)
.catch(function fail2(e) {
console.error("fail2: ", e);
});
// fail2: Error: error
// process.nextTick 微任务,但是会在下一个循环之前执行,优先执行。
process.nextTick(() => {
console.log("nextTick");
});
Promise.resolve().then(() => {
console.log("then");
});
// 宏任务,感觉类似 setTimeout
setImmediate(() => {
console.log("setImmediate");
});
console.log("end");
// end
// nextTick
// then
// setImmediate
console.log(1);
setTimeout(function () {
console.log(2);
let promise = new Promise(function (resolve, reject) {
console.log(7);
resolve();
}).then(function () {
console.log(8);
});
}, 1000);
setTimeout(function () {
console.log(10);
let promise = new Promise(function (resolve, reject) {
console.log(11);
resolve();
}).then(function () {
console.log(12);
});
}, 0);
let promise = new Promise(function (resolve, reject) {
console.log(3);
resolve();
})
.then(function () {
console.log(4);
})
.then(function () {
console.log(9);
});
console.log(5);
/// 1,3,5,4,9,10,11,12,2,7,8
async & await
try {
function test(id) {
return new Promise(async (resolve, reject) => {});
}
// 需要注意的是一个 function 只是 return 一个 promise 但是没有 resolve 或者 rejected 的话,await 是没有结果的,也不会进行赋值。
const a = await test();
console.log(a);
} catch (error) {
console.error("error", error);
}
// 此处 a 不会被打印
function wait() {
return new Promise((resolve) => setTimeout(resolve, 10 * 100));
}
async function main() {
console.time();
const x = wait();
const y = wait();
const z = wait();
await x;
await y;
await z;
console.timeEnd();
}
main();
// 1s 左右
// 因为异步已经在执行了
function wait() {
return new Promise((resolve) => setTimeout(resolve, 10 * 100));
}
async function main() {
console.time();
await wait();
await wait();
await wait();
console.timeEnd();
}
main();
// 3s 左右
promise 和 setTimeout 都会将事件放入异步队列,但 setTimeout 即便是写 0,也会有 4ms 的延迟
console.log("begin");
setTimeout(() => {
console.log("setTimeout 1");
Promise.resolve()
.then(() => {
console.log("promise 1");
setTimeout(() => {
console.log("setTimeout2");
});
// 同步 Promise.resolve
})
.then(() => {
console.log("promise 2");
});
new Promise((resolve) => {
console.log("a");
resolve();
}).then(() => {
console.log("b");
});
}, 0);
console.log("end");
// begin
// end
// setTimeout 1
// a
// promise 1
// b
// promise 2
// setTimeout2
因为 Promise 属于微任务,setTimeout 属于宏任务。
// 两个函数都会一直执行,卡死为止。
function a() {
console.log("a");
setTimeout(a);
}
a();
function b() {
console.log("b");
Promise.resolve().then(b);
}
b();
setTimeout(function () {
console.log("setTimeout1"); //8
new Promise(function (resolve) {
console.log("promise0"); //9
resolve();
}).then(function () {
console.log("settimeout promise resolveed"); //10
});
});
setTimeout(function () {
console.log("setTimeout2"); //11
});
const P = new Promise(function (resolve) {
console.log("promise"); //1
for (var i = 0; i < 10000; i++) {
if (i === 10) {
console.log("for"); //2
}
if (i === 9999) {
resolve("resolve");
}
}
})
.then(function (val) {
console.log("resolve1"); //5
})
.then(function (val) {
console.log("resolve2"); //7
});
new Promise(function (resolve) {
console.log("promise2"); //3
resolve("resolve");
}).then(function (val) {
console.log("resolve3"); //6
});
console.log("console"); //4